-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local skynet = require 'skynet'
local queue = require 'skynet.queue'
local class = require 'mc.class'
local utils = require 'mc.utils'
local log = require 'mc.logging'
local m_error_base = require 'messages.base'

local LOCK_FOREVER = 0xFFFF -- 用特殊的超时时间代表永久锁

local pwr_lock = class()

function pwr_lock:ctor(fructrl, type)
    self.type = type
    self.fructrl = fructrl
    self.system_id = fructrl.system_id
    self.power_lock_props = (type == 'on') and
    {
        ['record'] = 'PwrOnLockedRecord',
        ['locked'] = 'PwrOnLocked',
        ['reasons'] = 'Reasons'
    } or
    {
        ['record'] = 'PowerOffLockedRecord',
        ['locked'] = 'PowerOffLocked',
        ['reasons'] = 'PowerOffLockedReasons'
    }
    self.power_lock_queue = queue()
end

-- 维护PwrOnLockedRecord/PowerOffLockedRecord属性，记录上电锁/下电锁信息。每种锁记录一行信息，超时或解锁后删除。
-- 增
local function add_power_lock(self, locked, timeout, appname, reason)
    if not appname or not reason  or #reason > 128 then
        log:error("[System:%s]Appname or reason is detected as invalid (appname: %s, reason: %s)",
            self.fructrl.system_id ,appname, reason)
        return
    end
    local val = self.fructrl[self.power_lock_props['record']]
    local data = {
        appname = appname,
        reason = reason,
        locked = locked,
        timeout = timeout
    }
    val[appname .. '--' .. reason] = data
    self.fructrl[self.power_lock_props['record']] = val

    if timeout == LOCK_FOREVER then
        log:notice_easy('[System:%s]Attention, [%s] will forbid power %s forever!!!', self.fructrl.system_id, appname, self.type)
    else
        log:notice_easy('[System:%s][%s]Add power %s lock to lock_table successfully.', self.fructrl.system_id, appname, self.type)
    end
end

-- 删
local function del_power_lock(self, key)
    local val = self.fructrl[self.power_lock_props['record']]
    -- 不能用remove的方式删除，任务会退出
    val[key] = nil
    self.fructrl[self.power_lock_props['record']] = val
end

-- 改
local function update_power_lock(self, locked, timeout, appname, reason)
    if not appname or not reason  or #reason > 128 then
        log:error("[System:%s]Appname or reason is detected as invalid (appname: %s, reason: %s)",
            self.fructrl.system_id ,appname, reason)
        return
    end
    local val = self.fructrl[self.power_lock_props['record']]
    for _, v in pairs(val) do
        if v.appname == appname and v.reason == reason then
            v.locked = locked
            v.timeout = timeout
            log:notice_easy('[System:%s]Update power %s lock item successfully, appname=(%s), reason=(%s), locked=(%s),' ..
                ' timeout=(%d)', self.fructrl.system_id, self.type, appname, reason, locked, timeout)
            if timeout == LOCK_FOREVER then
                log:notice_easy('[System:%s]Attention, [%s] will forbid power %s forever!!!',
                     self.fructrl.system_id, appname, self.type)
            end
            self.fructrl[self.power_lock_props['record']] = val
            return true
        end
    end
    return false
end

-- 查
local function find_power_lock(self, appname, reason)
    local val = self.fructrl[self.power_lock_props['record']]
    for k, v in pairs(val) do
        if v.appname == appname and v.reason == reason then
            return k
        end
    end
end

local function get_reasons_in_table(self)
    local val = self.fructrl[self.power_lock_props['record']]
    local reasons = {}
    for _, v in pairs(val) do
        table.insert(reasons, v.reason)
    end
    return reasons
end

-- 例测任务
local function power_overtime_task(self, appname, reason)
    local key = find_power_lock(self, appname, reason)
    local val, is_lock
    if not key then
        log:error_easy('[System:%s]Get wrong key in power %s lock_table!!!', self.system_id, self.type)
        return
    end

    while true do
        skynet.sleep(100)
        is_lock = self.power_lock_queue(function()
            val = self.fructrl[self.power_lock_props['record']]
            val = utils.table_copy(val)
            -- 永久锁不能超时退出，只能解锁退出
            if not (val[key].timeout == LOCK_FOREVER) then
                val[key].timeout = val[key].timeout - 1
                self.fructrl[self.power_lock_props['record']] = val
            end
            -- 解锁
            if not val[key].locked then
                -- 删除锁
                del_power_lock(self, key)
                return false
            end
            -- 超时
            if val[key].timeout <= 0 then
                log:error_easy('[System:%s]Power %s lock is overtime!!!', self.system_id, type)
                -- 删除锁
                del_power_lock(self, key)
                return false
            end
            return true
        end)
        if not is_lock then
            break
        end
    end

    -- 超时或解锁都删除锁
    log:notice_easy('[System:%s]Delete power %s lock item, appname=(%s), reason=(%s)', self.system_id, self.type, appname, reason)
    val = self.fructrl[self.power_lock_props['record']]
    -- 如果表格空
    if next(val) == nil then
        -- 解锁
        self.fructrl[self.power_lock_props['locked']] = false
        log:notice('[System:%s]Power%sLocked has been unlocked', self.system_id, self.type)
    end

    self.fructrl[self.power_lock_props['reasons']] = get_reasons_in_table(self)
end

-- 入口函数
function pwr_lock:power_lock_entrance(locked, timeout, appname, reason)
    -- 入参判断，已对齐结论，合理值范围在10-3600s
    if (timeout ~= LOCK_FOREVER) and (timeout < 10 or timeout > 3600) then
        log:error_easy('[System:%s]Wrong input parameter when call SetPower%sLock!', self.system_id,
            self.type)
        error(m_error_base.PropertyValueNotInList('%PowerLockTimeout:' .. timeout, '%PowerLockTimeout'))
    end

    local val = utils.table_copy(self.fructrl[self.power_lock_props['record']])
    if not val then
        val = {}
        self.fructrl[self.power_lock_props['record']] = val
    end

    self.power_lock_queue(function()
        -- 先尝试找到对应项，并刷新数据（包括解锁）
        if update_power_lock(self, locked, timeout, appname, reason) then
            return
        end
        -- 表格里没有该项，并且为解锁
        if not locked then
            log:debug('[System:%s]Item is not exist in power%s_lock_table when unlock.', self.system_id, self.type)
            return
        end

        -- 加锁
        add_power_lock(self, locked, timeout, appname, reason)
        -- 刷新资源树,LockedRecord和LockedReasons
        self.fructrl[self.power_lock_props['locked']] = true
        self.fructrl[self.power_lock_props['reasons']] = get_reasons_in_table(self)
        -- 创建例测超时任务
        skynet.fork_once(function()
            power_overtime_task(self, appname, reason)
        end)
    end)
end

function pwr_lock:restart_task()
    local val = self.fructrl[self.power_lock_props['record']]
    for _, v in pairs(val) do
        skynet.fork_once(function()
            power_overtime_task(self, v.appname, v.reason)
        end)
    end

end

function pwr_lock:is_power_locked_by_upgrade()
    local lock_record = self.fructrl[self.power_lock_props['record']]
    if lock_record == nil then
        return false
    end

    local upgrade_mode_flag_table = {
        Upgrade = {
            bios = true
        },
        general_hardware = {
            cpld = true,
            Vrd = true,
            NPU_VRD = true,
            fpga = true
        }
    }

    for _, record in pairs(lock_record) do
        if upgrade_mode_flag_table[record.appname] and upgrade_mode_flag_table[record.appname][record.reason] then
            return true, string.upper(record.reason)
        end
    end
    return false
end

return pwr_lock
